﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FluentValidation;

namespace Entity.Validation.Core
{
    public abstract class ValidateEntity<T> : IValidateEntity where T : ValidateEntity<T>, new()
    {
        private InlineValidator<T> _validationRules;

        public InlineValidator<T> ValidationRules
        {
            get { return _validationRules ?? (_validationRules = new InlineValidator<T>()); }
            set { _validationRules = value; }
        }

        protected ValidateEntity()
        {
            this.CreateValidationRules();
        }

        protected virtual void CreateValidationRules()
        {
            var propertyCollection = this.GetType().GetProperties().Select(x => new { PropertyInfo = x, Attribute = (ValidateFieldAttribute)x.GetCustomAttributes(typeof(ValidateFieldAttribute), true).FirstOrDefault() }).Where(y => y.Attribute != null && y.Attribute.GetType() == typeof(ValidateFieldAttribute));

            foreach (var validationProperty in propertyCollection)
            {
                //var expression = CreateLamdaExpression(typeof (T), "x", validationProperty.PropertyInfo);
                object[] args = {typeof (T), "x", validationProperty.PropertyInfo};
                dynamic genericExpression = typeof(T).GetMethod("CreateLamdaExpressionOfGenericType",
                                                                 BindingFlags.NonPublic | BindingFlags.Instance)
                                                      .MakeGenericMethod(validationProperty.PropertyInfo.PropertyType)
                                                      .Invoke(this, args);

                if (validationProperty.Attribute.IsRequired)
                    DefaultValidatorExtensions.NotEmpty(ValidationRules.RuleFor(genericExpression));

                if (validationProperty.Attribute.IsEmail)
                    DefaultValidatorExtensions.EmailAddress(ValidationRules.RuleFor(genericExpression));

                if (validationProperty.Attribute.MinLength >= 0 && validationProperty.Attribute.MaxLength > 0)
                    DefaultValidatorExtensions.Length(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.MinLength, validationProperty.Attribute.MaxLength);

                if (validationProperty.Attribute.ExactLength > 0)
                    DefaultValidatorExtensions.Length(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.ExactLength);

                if (validationProperty.Attribute.LessThan > 0)
                    DefaultValidatorExtensions.LessThan(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.LessThan);

                if (validationProperty.Attribute.GreaterThan > 0)
                    DefaultValidatorExtensions.GreaterThan(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.GreaterThan);

                if (validationProperty.Attribute.LessThanOrEqual > 0)
                    DefaultValidatorExtensions.LessThanOrEqualTo(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.LessThanOrEqual);

                if (validationProperty.Attribute.GreaterThanOrEqual > 0)
                    DefaultValidatorExtensions.GreaterThanOrEqualTo(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.GreaterThanOrEqual);

                if (validationProperty.Attribute.IsCreditCard)
                    DefaultValidatorExtensions.CreditCard(ValidationRules.RuleFor(genericExpression));

                if (validationProperty.Attribute.RegexString.NotEmpty())
                    DefaultValidatorExtensions.Matches(ValidationRules.RuleFor(genericExpression), validationProperty.Attribute.RegexString);
                   
            }
        }

        protected Expression<Func<T, object>> CreateLamdaExpression(Type type, string parameterName, PropertyInfo propertyInfo)
        {
            ParameterExpression arg = Expression.Parameter(type, parameterName);
            Expression expr = null;

            expr = Expression.Property(arg, propertyInfo);
            if (propertyInfo.PropertyType.IsValueType)
                expr = Expression.Convert(expr, typeof(T));
            return Expression.Lambda<Func<T, object>>(expr, arg);
        }

        protected Expression<Func<T, TK>> CreateLamdaExpressionOfGenericType<TK>(Type type, string parameterName, PropertyInfo propertyInfo)
        {
            ParameterExpression arg = Expression.Parameter(type, parameterName);
            Expression expr = null;

            expr = Expression.Property(arg, propertyInfo);
            if (propertyInfo.PropertyType.IsValueType)
                expr = Expression.Convert(expr, propertyInfo.PropertyType);
            return Expression.Lambda<Func<T, TK>>(expr, arg);
        }
        public virtual IList<String> ValidateEntities()
        {
            var result = this.ValidationRules.Validate((T)this);
            if (result != null && result.Errors != null && result.Errors.Any())
            {
                return result.Errors.Select(validationFailure => validationFailure.ErrorMessage).ToList();
            }
            return null;
        }

    }
}